using System;
using Unity.Collections;
using UnityEngine.Rendering;
using UnityEngine;
using UnityEngine.Experimental.Rendering;

public class VUniversalRenderPipeline: RenderPipeline
{
    
    private VUniversalRenderPipelineAsset m_Asset;
    private const string m_BufferName = "RenderCamera";
    private static readonly ProfilingSampler m_ProfilingRender = new ProfilingSampler(m_BufferName);
    private CommandBuffer m_CommandBuffer = new CommandBuffer    
    {
        name = m_BufferName
    };
    
    DoubleBuffer<Vector2> m_projectionJitter = new DoubleBuffer<Vector2>((_) => new Vector2(.5f, .5f),2);
    private uint FrameCount;
    
    public static float Get(int index, int radix)
    {
        float result = 0f;
        float fraction = 1f / (float)radix;

        while (index > 0)
        {
            result += (float)(index % radix) * fraction;

            index /= radix;
            fraction /= (float)radix;
        }

        return result;
    }
    
    /// <summary>
    /// GenerateRandomOffset
    /// </summary>
    /// <returns></returns>
    public static Vector2 GenerateRandomOffset(int index)
    {
        // The variance between 0 and the actual halton sequence values reveals noticeable instability
        // in Unity's shadow maps, so we avoid index 0.
        var offset = new Vector2(
            Get((index & 1023) + 1, 2) - 0.5f,
            Get((index & 1023) + 1, 3) - 0.5f
        );
        

        return offset;
    }
    //长度为8的Halton序列
    private Vector2[] HaltonSequence = new Vector2[8]
    {
        GenerateRandomOffset(0),
        GenerateRandomOffset(1),
        GenerateRandomOffset(2),
        GenerateRandomOffset(3),
        GenerateRandomOffset(4),
        GenerateRandomOffset(5),
        GenerateRandomOffset(6),
        GenerateRandomOffset(7),
 
    };
    
    
    
    public static int maxVisibleAdditionalLights
    {
        get
        {
            return 16;
        }
    }
    
    public  VUniversalRenderPipeline(VUniversalRenderPipelineAsset vUniversalRenderPipelineAsset)
    {
        m_Asset = vUniversalRenderPipelineAsset;
      
        Gen_Irradiance_Map.IBL_Intensity = 0.2f;
        Gen_Irradiance_Map.SetIBL();
        FrameCount = 0;
    }
    void RenderSingleCamera(ScriptableRenderContext context, Camera camera)
    {
        var cameraData = new VCameraData();
        var renderData = new VRenderingData();
        InitializeCameraData(camera, out  cameraData);
        //
        //CullingResults
        camera.TryGetCullingParameters(out var cullingParameters);
        cullingParameters.shadowDistance = Mathf.Min(m_Asset.shadowSetting.shadowDistance,camera.farClipPlane - camera.nearClipPlane);

        var cullingResults = context.Cull(ref cullingParameters);
        
        InitializeRenderData(ref cameraData, ref cullingResults,out  renderData);
        
        
        var renderer = cameraData.renderer;
        
        renderer.Setup(context,ref renderData);
        ExecuteCmd(context, m_CommandBuffer);
        
        using (new ProfilingScope(m_CommandBuffer, m_ProfilingRender))
        {
            //set up finish
            renderer.Execute(context, ref renderData);
        }

       
        
        ExecuteCmd(context, m_CommandBuffer);
        renderer.Dispose();
    }
    public static void ExecuteCmd(ScriptableRenderContext context, CommandBuffer cmd)
    {
        if (context != null && cmd != null)
        {
            context.ExecuteCommandBuffer(cmd);
            cmd.Clear();
        }
    }
    void InitializeCameraData(Camera camera, out VCameraData cameraData)
    {
        cameraData.renderer = m_Asset.scriptableRenderer;
        cameraData.camera = camera;
        ///////////////////////////////////////////////////////////////////
        // Descriptor settings                                            /
        ///////////////////////////////////////////////////////////////////
        RenderTextureDescriptor desc;
        
        desc = new RenderTextureDescriptor(camera.pixelWidth, camera.pixelHeight);
        desc.width = (int)((float)desc.width );
        desc.height = (int)((float)desc.height );
        desc.graphicsFormat = GraphicsFormat.R16G16B16A16_SFloat;
        desc.depthBufferBits = 32;
        desc.msaaSamples = 1;
        desc.sRGB = (QualitySettings.activeColorSpace == ColorSpace.Linear);
    
        desc.width = Mathf.Max(1, desc.width);
        desc.height = Mathf.Max(1, desc.height);

        desc.enableRandomWrite = false;//?
        desc.bindMS = false;//?
        desc.useDynamicScale = camera.allowDynamicResolution;
        cameraData.Inv_Projection = Matrix4x4.identity;
        cameraData.cameraTargetDescriptor = desc;
    }

    void InitializeRenderData(ref VCameraData cameraData,ref CullingResults cullingResults, out VRenderingData renderingData)
    {
        
        renderingData.cameraData = cameraData;
        Camera camera = cameraData.camera;
        renderingData.shadowSetting = m_Asset.shadowSetting;
        var visibleLights = cullingResults.visibleLights;
        int mainLightIndex = GetMainLightIndex(visibleLights);
        InitializeLightData(visibleLights, mainLightIndex, out renderingData.lightData);
        // light data
        renderingData.cullResults = cullingResults;
        renderingData.postFXSettings = m_Asset.postFXSettings;
        if (m_Asset.postFXSettings)
        {
            renderingData.postProcessingEnabled = true;
        }
        else
        {
            renderingData.postProcessingEnabled = false;
        }
        //For TAA
        renderingData.ProjectionMatrix = camera.projectionMatrix;
        var nowProjectionJitter = m_projectionJitter.Current;
        var JitteredProjectionMat =GetJitteredPerspectiveProjectionMatrix(camera,nowProjectionJitter);
        
     
        renderingData.ViewMatrix = camera.worldToCameraMatrix;
        renderingData.JitteredProjectionMatrix = JitteredProjectionMat;

        renderingData.ProjectionJitter = new Vector2(nowProjectionJitter.x / camera.scaledPixelWidth,
            nowProjectionJitter.y / camera.scaledPixelHeight);
        renderingData.NextProjectionJitter = m_projectionJitter.Next;
        
        renderingData.FrameCount = FrameCount;

        //set IBL





    }

    public int GetMainLightIndex(NativeArray<VisibleLight> visibleLights)
    {
        int totalVisibleLights = visibleLights.Length;

        if (totalVisibleLights == 0)
            return -1;
        Light sunLight = RenderSettings.sun;
        int brightestDirectionalLightIndex = -1;
        float brightestLightIntensity = 0.0f;
        for (int i = 0; i < totalVisibleLights; ++i)
        {
            VisibleLight currVisibleLight = visibleLights[i];
            Light currLight = currVisibleLight.light;

            // Particle system lights have the light property as null. We sort lights so all particles lights
            // come last. Therefore, if first light is particle light then all lights are particle lights.
            // In this case we either have no main light or already found it.
            if (currLight == null)
                break;

            if (currVisibleLight.lightType == LightType.Directional)
            {
                // Sun source needs be a directional light
                if (currLight == sunLight)
                    return i;

                // In case no sun light is present we will return the brightest directional light
                if (currLight.intensity > brightestLightIntensity)
                {
                    brightestLightIntensity = currLight.intensity;
                    brightestDirectionalLightIndex = i;
                }
            }
        }
        return brightestDirectionalLightIndex;
    }
    public void InitializeLightData(NativeArray<VisibleLight> visibleLights, int mainLightIndex, out VLightData lightData)
    {
        lightData.visibleLights = visibleLights;
        lightData.mainLightIndex = mainLightIndex;
        lightData.supportsAdditionalLights = true;
        int maxVisibleAdditionalLights = VUniversalRenderPipeline.maxVisibleAdditionalLights;
        lightData.additionalLightsCount =
            Math.Min((mainLightIndex != -1) ? visibleLights.Length - 1 : visibleLights.Length,
                maxVisibleAdditionalLights);
    }
    protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
    {
        BeginFrameRendering(renderContext, cameras);
        for (int i = 0; i < cameras.Length; ++i)
        {
            var camera = cameras[i];
            BeginCameraRendering(renderContext, camera);
            RenderSingleCamera(renderContext, camera);
            EndCameraRendering(renderContext, camera);
        }
        EndFrameRendering(renderContext, cameras);
        renderContext.Submit();
        
        FrameCount++;
        
        m_projectionJitter.Next = HaltonSequence[FrameCount % 8];
        m_projectionJitter.OnlyFlip();
    }
    
    public static int sampleIndex { get; private set; }
    const int k_SampleCount = 8;
    
    public static Matrix4x4 GetJitteredPerspectiveProjectionMatrix(Camera camera, Vector2 offset)
    {
        float near = camera.nearClipPlane;
        
        float vertical = Mathf.Tan(0.5f * Mathf.Deg2Rad * camera.fieldOfView) * near;
        float horizontal = vertical * camera.aspect;

        offset.x *= horizontal / (0.5f * camera.pixelWidth);
        offset.y *= vertical / (0.5f * camera.pixelHeight);

        var matrix = camera.projectionMatrix;

        matrix[0, 2] += offset.x / horizontal;
        matrix[1, 2] += offset.y / vertical;

        return matrix;
    }
}